热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

表里|层面_ShardingJDBC第一篇:分库分表

篇首语:本文由编程笔记#小编为大家整理,主要介绍了ShardingJDBC第一篇:分库分表相关的知识,希望对你有一定的参考价值。文章目录

篇首语:本文由编程笔记#小编为大家整理,主要介绍了ShardingJDBC第一篇:分库分表相关的知识,希望对你有一定的参考价值。



文章目录


  • 一、前言
  • 二、关系型数据库层面的高并发优化
    • 2.1 MYSQL海量数据带来的性能问题
    • 2.2 分片策略
    • 2.3 分布式ID
      • 2.3.1 定义全局表
      • 2.3.2 UUID
      • 2.3.3 雪花算法


  • 三、Sharding-JDBC分库分表
    • 3.1 Sharding-JDBC内置算法
    • 3.2 取模算法
    • 3.3 基于分片容量的范围分片算法
    • 3.4 基于分片边界的范围分片算法
    • 3.5 自动时间段分片算法
    • 3.6 同时分库分表

  • 四、尾声


一、前言

本文从高并发出发,主要介绍了ShardingJDBC分库分表。

本文代码如下:sharding-jdbc-split分库分表demo工程代码


二、关系型数据库层面的高并发优化

2.1 mysql海量数据带来的性能问题

根据阿里开发手册, 单表行数超过500W或者单表数据容量超过2G,需要考虑水平分表;根据mysql官网,一个表中列数超过1017列,开发者记住1000列就好,需要考虑垂直分表。即行数和数据容量不够,水平分表,列数不够,垂直分表,如下:



eg: 水平有水平分表和水平分库,垂直有垂直分表(商品表、商品明细表)和 垂直分库(订单库、商品库、用户库)


水平分表涉及的问题有三个:

第一个问题是确定分片策略:水平分表存在一个无法避免的问题是,需要决定根据哪个字段将一个表拆分为多个表,这就是分片字段,决定使用哪个分片字段、决定如何对目标表分片的就是分片策略,常见的分片策略方案包括:哈希取模分片算法、一致性哈希分片算法(哈希环偏斜)、范围分片算法。

第二个问题是确定分布式ID:需要额外自定义一个字段来存储全局唯一的主键,即分布式ID,因为mysql的物理主键只能保证在一个表内唯一,水平分表将一个表变为多个表,而这个数据库的水平分表对前端来说是透明的,前端需要做 select * from tablename where 主键列= ‘xxx’ 这种类型操作,不能再用id列,常见的方式是水平分表的每个表增加一个额外的列,用uuid或者雪花算法保证唯一。都能保证唯一性,但是用雪花算法比uuid要好,因为uuid没有任何意义,雪花算法可以存储时间戳。

第三个问题是引入依赖:需要让后端Java代码中像操作一个表一样,去操作水平分表之后的多个表,要么在服务端代码中引入Sharding-JDBC依赖,要么是在linux上独立安装ShardingSphere这个中间件,两者的原理是一样的,只是一个在服务端代码层面实现,一个在数据库层面实现。


2.2 分片策略

哈希取模分片:通过表中的某一个字段进行hash算法得到一个哈希值,然后通过取模运算确定数据应该放在哪个分片中,这种方式非常适合随机读写的场景中,它能够很好的将一个大表的数据随机分散到多个小表,前提是哈希算法要设计的好,就是要均衡,如下:

哈希取模运算最大的优点是简单,“先哈希再取模”就好了,最大的缺点是无法适应业务需求的变化,假设根据当前数据表的量以及增长情况,我们把一个大表拆分成了4个小表,看起来满足目前的需求,但是经过一段时间的运行后,发现四个表不够,需要再增加4个表来存储,就是一共需要8个表,这种情况下,就需要对原来的数据进行整体迁移,这个过程非常麻烦。即一旦目标表或者数据库发生数量上的变化,就会导致所有数据都需要进行迁移,为了减少这种大规模的数据影响,所以引入了一致性hash算法,这也是现实开发中更常见的一种做法。

一致性哈希算法:将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数的值空间为 0 ~ 2^ 32 -1 ,就是我们通过 0 ~ 2^ 32 -1 的数字组成一个虚拟的圆环,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^ 32 -1,也就是说0点左侧的第一个点代表2^ 32
-1。我们把这个由2的32次方个点组成的圆环称为hash环。

假设现在水平分表出四个表,table_1、table_2、table_3、table_4,在一致性hash算法中,取模运算不是直接对这四个表来完成,而是对2^ 32来实现。hash(table编号)%2^ 32 通过上述公式算出的结果一定是一个0到2^ 32-1之间的一个整数,然后在这个数对应的位置标注目标表,四个表通过hash取模之后分别落在hash环的某个位置上,如下:


当添加一条数据时,同样通过hash和hash环取模运算得到一个目标值,然后根据目标值所在的hash环的位置顺时针查找最近的一个目标表,把数据存储到这个目标表中即可,所以,哈希环的作用也是帮助我们确定某条记录最终要插入到哪个表中,需要到哪个表中找到某条记录,如下:


一致性哈希运算不是直接面向目标表,而是面向hash环,这样的好处就是当需要删除某张表或者增加表的时候,对于整个数据变化的影响是局部的,而不是全局。即插入和删除一个表,只会影响后面那个表。增加一个表,只需要将一些数据从新表的下一个表的移动到新表就可以了;删除一个表,只需要将删除表里面的数据移动到下一个表里就可以了。

理论上,各个表表是能够均衡的分布在整个hash环中,然后每个新插入的记录就是均衡的分布到水平分表的各个表里面,但实际情况如下:

也就是产生了hash环偏斜的现象,这种现象导致的问题就是大量的数据都会保存到同一个表中,导致数据分配极度不均匀,如上图,很多数据会被存入到表table_01中。

解决的办法是把这四个节点分别复制一份出来分散到这个hash环中,这个复制出来的节点叫虚拟节点,根据实际需要可以虚拟出多个节点出来,注意这里是按实际需要来,比如可以给table_02、table_03、table_04各虚拟出一个,也可以给table_02虚拟出三个。

范围分片:基于数据表的业务特性,按照某种范围拆分,这个范围的有很多含义,比如:

① 时间范围,比如我们按照数据创建时间,按照每一个月保存一个表。基于时间划分还可以用来做冷热数据分离,越早的数据访问频次越少。
② 区域范围,区域一般指的是地理位置,比如一个表里面存储了来自全国各地的数据,如果数据量较大的情况下,可以按照地域来划分多个表。
③ 数据范围,比如根据某个字段的数据区间来进行划分。


2.3 分布式ID

分布式ID的特性
① 唯一性:确保生成的ID是全局唯一的。
② 有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
③ 高可用性:确保任何时候都能正确的生成ID。
④ 带时间:ID里面包含时间,一眼扫过去就知道哪天的数据

分布式id方案大概有:
① 数据库自增ID(定义全局表)
② UUID
③ Twitter-Snowflake算法


2.3.1 定义全局表

在数据库中专门创建一张序列表,利用数据库表中的自增ID来为其他业务的数据生成一个全局ID,那么每次要用ID的时候,直接从这个表中获取即可。

CREATE TABLE `uid_table` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `business_id` int(11) NOT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE (business_type) )

在应用程序中,每次调用下面这段代码,就可以持续获得一个递增的ID。

begin
replace into uid_table (business_id) values(2);
select last_insert_id();
commit;

其中,replace into是每次删除原来相同的数据,同时加1条,就能保证我们每次得到的就是一个自增的ID。

优点:
非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
ID号单调自增,可以实现一些对ID有特殊要求的业务。
缺点:
强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用
性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
ID发号性能瓶颈限制在单台MySQL的读写性能。


2.3.2 UUID

UUID的格式是: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 8-4-4-4-12共36个字符,它是一个128bit的
二进制转化为16进制的32个字符,然后用4个 - 连接起来的字符串。

UUID的五种生成方式
① 基于时间的UUID(date-time & MAC address): 主要依赖当前的时间戳及机器mac地址,因此
可以保证全球唯一性。(使用了Mac地址,因此会暴露Mac地址和生成时间。)
② 分布式安全的UUID(date-time & group/user id)将版本1的时间戳前四位换为POSIX的UID或
GID。
③ 基于名字空间的UUID-MD5版(MD5 hash & namespace),基于指定的名字空间/名字生成MD5
散列值得到,标准不推荐。
④ 基于随机数的UUID(pseudo-random number):基于随机数或伪随机数生成。
⑤ 基于名字空间的UUID-SHA1版(SHA-1 hash & namespace):将版本3的散列算法改为SHA1。

在Java中,提供了基于MD5算法的UUID、以及基于随机数的UUID。

优点:本地生成,没有网络消耗,生成简单,没有高可用风险。

缺点:
① 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
② 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅
丽莎病毒的制作者位置。
③ 无序查询效率低:由于生成的UUID是无序不可读的字符串,所以其查询效率低。
④ UUID不适合用来做数据库的唯一ID,如果用UUID做主键,无序的不递增,大家都知道,主键是有
索引的,然后mysql的索引是通过b+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的b+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键的b+树进行很大的修改,严重影响性能


2.3.3 雪花算法

SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型
的数字作为全局唯一 id。雪花算法的组成,一共64bit,这64个bit位由四个部分组成。

第一部分,1bit位,用来表示符号位,而ID一般是正数,所以这个符号位一般情况下是0。

第二部分,占41 个 bit:表示的是时间戳,是系统时间的毫秒数,但是这个时间戳不是当前系统的
时间,而是当前 系统时间-开始时间 ,即表示这个ID生成方案的使用的时间,时间戳是为了保证有序性,可读性,即开发者一看就能猜到ID是什么时候生成的。41位可以2^ 41 - 1表示个数字,可以表示的数值范围是:0 至 2^ 41-1,也就是说41位可以表示2^ 41-1个毫秒的值,转化成单位年则是(2^ 41-1)/1000 * 60 * 60 * 24*365=69年,也就是能容纳69年的时间。

第三部分,用来记录工作机器id,id包含10bit,意味着这个服务最多可以部署在 2^10 台机器上,
也就是 1024 台机器。其中这10bit又可以分成2个5bit,前5bit表示机房id、5bit表示机器id,意味着最多支持2^5个机房(32),每个机房可以支持32台机器。

第四部分,由12bit组成,表示一个递增序列,用来记录同毫秒内产生的不同id。如果是同一毫秒同一台机器来请求,需要使用序列号来保证唯一性,即保证同一毫秒内同一机器生成的ID是唯一的,这个其实就是为了满足我们ID的这个高并发,就是保证我同一毫秒进来的并发场景的唯一性。12位(bit)可以表示的最大正整数是2^12-1=4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。12位2进制,如果全部都是1的情况下,那么最终的值就是4095,也就是12bit能够存储的最大的数字是4095。


三、Sharding-JDBC分库分表

3.1 Sharding-JDBC内置算法

访问官网 https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/builtin-algorithm/sharding/ ,主要包括四种

分片算法:用来分库分表,常见的就是取模
分布式序列算法:用来生成全局id,包括uuid和雪花算法
负载均衡算法:用来做读写分离,多个库中选择一个写库进行操作
加密算法:用来加密解密



这里将分库分表,所有主要看分片算法就好,分布式序列算法就直接用雪花算法。


3.2 取模算法

取模算法需要我们自己实现一个算法,直接取模操作,如下:

运行生成四个表,如下:

其实还有一种哈希取模分片算法,即定位到 HashModShardingAlgorithm 类的 doSharding 方法,先哈希,再取模,如下:


3.3 基于分片容量的范围分片算法

基于分片容量的范围分片算法就是VOLUME_RANGE,如下:



插入600条数据,且按照user_id分表,每个表就是200条,所以运行之后生成三个表,每个表中200条数据


3.4 基于分片边界的范围分片算法


运行生成四个表,按照userid字段值分表,如下:


3.5 自动时间段分片算法


运行生成13个表,如下:


3.6 同时分库分表

上面都只有将一个表的数据存放到同一个库的多个表中,其实我们可以将一个表存放到多个库多个表中,如下:





未水平分表,数据仅在一个表中,查询语句走 “索引字段”;
水平分表后,之后从查询语句要走 “分片字段+索引字段” 。



四、尾声

本文从高并发出发,主要介绍了ShardingJDBC分库分表,本文代码如下:sharding-jdbc-split分库分表demo工程代码

天天打码,天天进步!!


推荐阅读
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • HashMap的扩容知识详解
    本文详细介绍了HashMap的扩容知识,包括扩容的概述、扩容条件以及1.7版本中的扩容方法。通过学习本文,读者可以全面了解HashMap的扩容机制,提升对HashMap的理解和应用能力。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了源码分析--ConcurrentHashMap与HashTable(JDK1.8)相关的知识,希望对你有一定的参考价值。  Concu ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • “你永远都不知道明天和‘公司的意外’哪个先来。”疫情期间,这是我们最战战兢兢的心情。但是显然,有些人体会不了。这份行业数据,让笔者“柠檬” ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文介绍了JavaScript进化到TypeScript的历史和背景,解释了TypeScript相对于JavaScript的优势和特点。作者分享了自己对TypeScript的观察和认识,并提到了在项目开发中使用TypeScript的好处。最后,作者表示对TypeScript进行尝试和探索的态度。 ... [详细]
author-avatar
元元木樨_669
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有